package com.lidong.lowcode.database.init.intf.impl;

import com.alibaba.druid.pool.DruidDataSource;
import com.alibaba.druid.pool.DruidPooledConnection;
import com.lidong.lowcode.database.annotation.*;
import com.lidong.lowcode.database.common.InitDataBaseException;
import com.lidong.lowcode.database.config.DataBaseConfig;
import com.lidong.lowcode.database.constants.BaseConst;
import com.lidong.lowcode.database.constants.database.CharacterSetEnum;
import com.lidong.lowcode.database.constants.database.EngineEnum;
import com.lidong.lowcode.database.constants.database.SqlColumnEnum;
import com.lidong.lowcode.database.constants.database.TableOrderEnum;
import com.lidong.lowcode.database.constants.enumconst.SqlTypeEnum;
import com.lidong.lowcode.database.constants.enumconst.StrCaseEnum;
import com.lidong.lowcode.database.entity.database.*;
import com.lidong.lowcode.database.init.core.CreateTableManage;
import com.lidong.lowcode.database.init.core.InitDataBaseContext;
import com.lidong.lowcode.database.init.intf.InitDataBase;
import com.lidong.lowcode.database.mapper.InitDataBaseMapper;
import com.lidong.lowcode.database.utils.LowCodeDataBaseResourceLoader;
import org.junit.jupiter.api.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Service;
import org.springframework.util.ObjectUtils;

import java.lang.reflect.Field;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;

/**
 * @author LiDong
 * @version 1.0.0
 * @createTime 9/9/2022 8:40 PM
 */
@Service
public class InitDataBaseImpl implements InitDataBase {

    private static final Logger logger = LoggerFactory.getLogger(InitDataBaseImpl.class);

    @Autowired
    private DruidDataSource dataSource;

    @Autowired
    private InitDataBaseMapper initDataBaseMapper;

    @Autowired
    private DataBaseConfig dataBaseConfig;

    /**
     * 数据库初始化开始
     *
     * @param dataBaseTableList 扫描到所有需要建表
     */
    @Override
    public void start(List<DataBaseTable> dataBaseTableList) {
        try {
            // 初始化上下文
            InitDataBaseContext initDataBaseContext = initDataBaseContext();
            if (!ObjectUtils.isEmpty(dataBaseTableList)) {
                initDataBaseContext.setDataBaseTableList(dataBaseTableList);
            }
            // 获取数据库建表实例
            CreateTableManage createTableManage = new CreateTableManage();
            createTableManage.setInitDataBaseContext(initDataBaseContext);
            // 初始 jdbcTemplate
            initJdbcTemplate(initDataBaseContext);
            // 初始建表记录
            createTableManage.start();
        } catch (Exception e) {
            logger.error("Error occur while initDataBase :{}", e.getMessage(), e);
        }
    }

    /**
     * 创建数据库建表固化表
     */
    @Override
    public void createCuringTable() {
        initDataBaseMapper.createBCreateTable();
        logger.info("Module:lowcode-database:Created curing table b_create_table!");
    }

    /**
     * 初始化 上下文中的 JdbcTemplate
     *
     * @param initDataBaseContext 上下文
     */
    private void initJdbcTemplate(InitDataBaseContext initDataBaseContext) {
        JdbcTemplate jdbcTemplate = new JdbcTemplate();
        jdbcTemplate.setDataSource(dataSource);
        initDataBaseContext.setJdbcTemplate(jdbcTemplate);
    }

    /**
     * 初始化上下文
     *
     * @return InitDataBaseContext
     * @throws SQLException
     */
    private InitDataBaseContext initDataBaseContext() throws SQLException {
        String dbType = dataSource.getDbType();
        SqlTypeEnum sqlType;
        if (SqlTypeEnum.MYSQL.name().equalsIgnoreCase(dbType)) {
            sqlType = SqlTypeEnum.MYSQL;
        } else if (SqlTypeEnum.ORACLE.name().equalsIgnoreCase(dbType)) {
            sqlType = SqlTypeEnum.ORACLE;
        } else if (SqlTypeEnum.POSTGRESQL.name().equalsIgnoreCase(dbType)) {
            sqlType = SqlTypeEnum.POSTGRESQL;
        } else {
            throw new InitDataBaseException("请检查数据源配置的类型!");
        }
        DruidPooledConnection druidPooledConnection = dataSource.getConnection();
        return new InitDataBaseContext(sqlType, druidPooledConnection);
    }

    /**
     * 扫描所有注解标注的表
     *
     * @return List
     */
    @Autowired
    public List<DataBaseTable> scanAnnotationGetTableInfo() {
        // List<Class<Table>> allClassByAnnotationList = ClassUtils.getAllClassByAnnotation(Table.class);
        String modelPack = dataBaseConfig.getModelPack();
        // 拆成多个pack，支持多个
        String[] packs = BaseConst.PATTERN_ONE.split(modelPack);
        List<Class<Table>> allClassByAnnotationList = LowCodeDataBaseResourceLoader.getAllClassByAnnotation(packs, Table.class);
        List<DataBaseTable> allTableList = new ArrayList<>();
        for (Class<Table> clazz : allClassByAnnotationList) {
            DataBaseTable dataBaseTable = initDataBaseTable(clazz);
            if (Objects.isNull(dataBaseTable) || ObjectUtils.isEmpty(dataBaseTable.getTableName())) {
                continue;
            }
            iniTableColumn(dataBaseTable, clazz);
            initTableIndex(dataBaseTable, clazz);
            initTablePrimaryKey(dataBaseTable, clazz);
            initTableUniqueKey(dataBaseTable, clazz);
            initTableVersion(dataBaseTable, clazz);
            allTableList.add(dataBaseTable);
        }
        // 构建每张表的外键 所有的表和列的信息构建完成后再构建外键
        initTableForeignKey(allTableList, allClassByAnnotationList);
        return allTableList;
    }

    /**
     * 扫描 TableVersion 注解 初始化表的 version 字段
     *
     * @param dataBaseTable 表
     * @param clazz         class
     */
    private void initTableVersion(DataBaseTable dataBaseTable, Class<Table> clazz) {
        TableVersion tableVersionAnnotation = clazz.getAnnotation(TableVersion.class);
        String version;
        if (ObjectUtils.isEmpty(tableVersionAnnotation)) {
            version = String.valueOf(System.currentTimeMillis());
        } else {
            version = tableVersionAnnotation.version();
        }
        dataBaseTable.setVersion(version);
    }

    /**
     * 构建唯一索引
     *
     * @param dataBaseTable 表
     * @param clazz         class
     */
    private void initTableUniqueKey(DataBaseTable dataBaseTable, Class<Table> clazz) {
        TableUniqueKey uniqueKeyAnnotation = clazz.getAnnotation(TableUniqueKey.class);
        List<DataBaseColumn> dataBaseColumnList = dataBaseTable.getDataBaseColumnList();
        List<DataBaseUniqueKey> dataBaseUniqueKeyList = new ArrayList<>();
        if (Objects.nonNull(uniqueKeyAnnotation)) {
            String[] columns = uniqueKeyAnnotation.columns();
            for (String column : columns) {
                DataBaseUniqueKey dataBaseUniqueKey = new DataBaseUniqueKey();
                List<DataBaseColumn> tempColumnList = new ArrayList<>();
                if (column.contains(BaseConst.COLON)) {
                    String[] nameAndColumn = column.split(BaseConst.COLON);
                    String name = nameAndColumn[0];
                    dataBaseUniqueKey.setName(name);
                    String columnStr = nameAndColumn[1];
                    String[] columnArray = columnStr.split(BaseConst.COMMA);
                    // 联合索引
                    for (String columnName : columnArray) {
                        for (DataBaseColumn dataBaseColumn : dataBaseColumnList) {
                            if (columnName.equalsIgnoreCase(dataBaseColumn.getName())) {
                                tempColumnList.add(dataBaseColumn);
                                break;
                            }
                        }
                    }
                }
                dataBaseUniqueKey.setColumns(tempColumnList);
                dataBaseUniqueKeyList.add(dataBaseUniqueKey);
            }
        }
        dataBaseTable.setUniqueKeyList(dataBaseUniqueKeyList);
    }

    /**
     * 构建表主键
     *
     * @param dataBaseTable 数据库表
     * @param clazz         clazz
     */
    private void initTablePrimaryKey(DataBaseTable dataBaseTable, Class<Table> clazz) {
        TablePrimaryKey primaryKeyAnnotation = clazz.getAnnotation(TablePrimaryKey.class);
        List<DataBaseColumn> columnList = dataBaseTable.getColumnList();
        DataBasePrimaryKey primaryKey = new DataBasePrimaryKey();
        List<DataBaseColumn> tempColumnList = new ArrayList<>();
        StringBuilder tempName = new StringBuilder();
        if (Objects.isNull(primaryKeyAnnotation)) {
            // column 有多个联合主键
            for (DataBaseColumn dataBaseColumn : columnList) {
                if (dataBaseColumn.isPrimaryKey()) {
                    tempName.append(dataBaseColumn.getName()).append(BaseConst.SUFFIX_PK);
                    tempColumnList.add(dataBaseColumn);
                    break;
                }
            }

        } else {
            String name = primaryKeyAnnotation.name();
            String[] columns = primaryKeyAnnotation.columns();
            // column 有多个联合主键
            for (String column : columns) {
                for (DataBaseColumn dataBaseColumn : columnList) {
                    if (column.equalsIgnoreCase(dataBaseColumn.getName())) {
                        tempName.append(dataBaseColumn.getName());
                        tempColumnList.add(dataBaseColumn);
                        break;
                    }
                }
            }
            if (ObjectUtils.isEmpty(name) && !ObjectUtils.isEmpty(tempName)) {
                tempName.append(BaseConst.SUFFIX_PK);
            } else {
                tempName = new StringBuilder(name);
            }
        }
        primaryKey.setName(tempName.toString());
        primaryKey.setColumns(tempColumnList);
        dataBaseTable.setPrimaryKey(primaryKey);
    }

    /**
     * 构建表的外键
     *
     * @param dataBaseTable            表
     * @param allClassByAnnotationList clazz
     */
    private void initTableForeignKey(List<DataBaseTable> dataBaseTable, List<Class<Table>> allClassByAnnotationList) {
        for (Class<Table> clazz : allClassByAnnotationList) {
            TableForeignKey foreignKeyAnnotation = clazz.getAnnotation(TableForeignKey.class);
            if (Objects.nonNull(foreignKeyAnnotation)) {
                String[] columns = foreignKeyAnnotation.columns();
            }
        }
    }

    /**
     * 构建表的索引信息
     *
     * @param dataBaseTable 表
     * @param clazz         clazz
     */
    private void initTableIndex(DataBaseTable dataBaseTable, Class<Table> clazz) {
        TableIndex tableIndexAnnotation = clazz.getAnnotation(TableIndex.class);
        List<DataBaseColumn> dataBaseColumnList = dataBaseTable.getDataBaseColumnList();
        List<DataBaseIndex> dataBaseIndexList = new ArrayList<>();
        if (Objects.nonNull(tableIndexAnnotation)) {
            String[] columns = tableIndexAnnotation.columns();
            for (String column : columns) {
                DataBaseIndex dataBaseIndex = new DataBaseIndex();
                List<DataBaseColumn> tempColumnList = new ArrayList<>();
                if (column.contains(BaseConst.COLON)) {
                    String[] nameAndColumn = column.split(BaseConst.COLON);
                    String name = nameAndColumn[0];
                    dataBaseIndex.setName(name);
                    String columnStr = nameAndColumn[1];
                    String[] columnArray = columnStr.split(BaseConst.COMMA);
                    // 联合索引
                    for (String columnName : columnArray) {
                        for (DataBaseColumn dataBaseColumn : dataBaseColumnList) {
                            if (columnName.equalsIgnoreCase(dataBaseColumn.getName())) {
                                tempColumnList.add(dataBaseColumn);
                                break;
                            }
                        }
                    }
                }
                dataBaseIndex.setColumns(tempColumnList);
                dataBaseIndexList.add(dataBaseIndex);
            }
        }
        dataBaseTable.setDataBaseIndexList(dataBaseIndexList);
    }

    /**
     * 初始化表的字段
     *
     * @param dataBaseTable 表
     * @param clazz         用 Table 注解标注的类
     */
    private void iniTableColumn(DataBaseTable dataBaseTable, Class<Table> clazz) {
        Field[] declaredFields = clazz.getDeclaredFields();
        List<DataBaseColumn> columnList = new ArrayList<>();
        for (Field declaredField : declaredFields) {
            Column columnAnnotation = declaredField.getAnnotation(Column.class);
            if (Objects.isNull(columnAnnotation)) {
                continue;
            }
            ColumnNameCase columnNameCaseAnnotation = declaredField.getAnnotation(ColumnNameCase.class);
            ColumnRemark columnRemarkAnnotation = declaredField.getAnnotation(ColumnRemark.class);
            DefaultValue defaultValueAnnotation = declaredField.getAnnotation(DefaultValue.class);
            ColumnType columnTypeAnnotation = declaredField.getAnnotation(ColumnType.class);
            // 组装列
            DataBaseColumn dataBaseColumn = new DataBaseColumn();
            dataBaseColumn.setName(columnAnnotation.name());
            dataBaseColumn.setDataType(columnAnnotation.dataType());
            dataBaseColumn.setCharacter(columnAnnotation.characterSet());
            dataBaseColumn.setRemark(columnAnnotation.remark());
            dataBaseColumn.setPrimaryKey(columnAnnotation.isPrimaryKey());
            dataBaseColumn.setAutoIncrement(columnAnnotation.autoIncrement());
            dataBaseColumn.setLength(columnAnnotation.length());
            dataBaseColumn.setDecimalLength(columnAnnotation.decimalLength());
            dataBaseColumn.setDefaultValue(columnAnnotation.defaultValue());
            dataBaseColumn.setNameCase(columnAnnotation.nameCase());
            dataBaseColumn.setNotNull(columnAnnotation.notNull());
            if (Objects.nonNull(columnNameCaseAnnotation)) {
                StrCaseEnum tempColumnCase = columnNameCaseAnnotation.nameCase();
                dataBaseColumn.setNameCase(tempColumnCase);
            }
            if (Objects.nonNull(columnRemarkAnnotation)) {
                String tempRemarkValue = columnRemarkAnnotation.value();
                dataBaseColumn.setRemark(tempRemarkValue);
            }
            if (Objects.nonNull(defaultValueAnnotation)) {
                String tempDefaultValue = defaultValueAnnotation.value();
                dataBaseColumn.setDefaultValue(tempDefaultValue);
            }
            if (Objects.nonNull(columnTypeAnnotation)) {
                SqlColumnEnum tempColumnType = columnTypeAnnotation.type();
                int tempColumnLength = columnTypeAnnotation.length();
                int tempColumnDecimalLength = columnTypeAnnotation.decimalLength();
                dataBaseColumn.setDataType(tempColumnType);
                if (0 != tempColumnLength) {
                    dataBaseColumn.setLength(tempColumnLength);
                }
                if (0 != tempColumnDecimalLength) {
                    dataBaseColumn.setDecimalLength(tempColumnDecimalLength);
                }
            }
            columnList.add(dataBaseColumn);
        }
        if (Objects.nonNull(dataBaseTable) && !ObjectUtils.isEmpty(dataBaseTable.getTableName())) {
            dataBaseTable.setColumnList(columnList);
        }
    }

    /**
     * 构建 databaseTable
     *
     * @param clazz clazz
     * @return DataBaseTable
     */
    private DataBaseTable initDataBaseTable(Class<Table> clazz) {
        Table tableAnnotation = clazz.getAnnotation(Table.class);
        if (Objects.isNull(tableAnnotation)) {
            return null;
        }
        TableNameCase tableNameCaseAnnotation = clazz.getAnnotation(TableNameCase.class);
        TableRemark tableRemarkAnnotation = clazz.getAnnotation(TableRemark.class);
        UpdateTableTime updateTableTimeAnnotation = clazz.getAnnotation(UpdateTableTime.class);
        EnableTimeSuffix enableTimeSuffixAnnotation = clazz.getAnnotation(EnableTimeSuffix.class);
        String name = tableAnnotation.name();
        String comment = tableAnnotation.comment();
        StrCaseEnum strCaseEnum = tableAnnotation.tableNameCase();
        int autoIncrementNum = tableAnnotation.autoIncrementNum();
        TableOrderEnum tableOrderEnum = tableAnnotation.orderType();
        CharacterSetEnum characterSetEnum = tableAnnotation.characterSet();
        EngineEnum engine = tableAnnotation.engine();
        DataBaseTable dataBaseTable = new DataBaseTable(name, comment, strCaseEnum, autoIncrementNum, tableOrderEnum, characterSetEnum, engine);
        if (Objects.nonNull(tableNameCaseAnnotation)) {
            dataBaseTable.setTableNameCase(tableNameCaseAnnotation.nameCase());
        }
        if (Objects.nonNull(tableRemarkAnnotation)) {
            dataBaseTable.setRemark(tableRemarkAnnotation.value());
        }
        if (Objects.nonNull(updateTableTimeAnnotation)) {
            dataBaseTable.setCreateOrUpdateTime(updateTableTimeAnnotation.dataTime());
        }
        if (Objects.nonNull(enableTimeSuffixAnnotation) && enableTimeSuffixAnnotation.value()) {
            dataBaseTable.setNameTimeSuffix(enableTimeSuffixAnnotation.pattern());
        }
        return dataBaseTable;
    }

    @Test
    public void test() {
        scanAnnotationGetTableInfo();
    }
}
